using System;
using System.Collections.Generic;
using System.Text;
using Castle.DynamicProxy;
using System.Xml;
using System.IO;
using Sio.Mdm.Utils;
using Sio.Mdm.Server;
using Castle.Core.Interceptor;

namespace Sio.Mdm.Client
{
    /// <summary>
    /// Implementation of client helper for retreiving services from server
    /// </summary>
    public class ServerInformation : IInterceptor
    {
        #region Fields
        /// <summary>
        /// Cached interfaces based on service name.
        /// </summary>
        private static Dictionary<string, Type> ServiceInterfaces = new Dictionary<string, Type>(); 
        #endregion

        #region Properties

        #region Public

        #region ServerType
        private ServerType _serverType = ServerType.Disk;
        /// <summary>
        /// Type of server we're using. This will return requested services
        /// </summary>
        public ServerType ServerType
        {
            get
            {
                return _serverType;
            }
            set
            {
                if (_serverInstance != null)
                {
                    throw new ArgumentException("Server type cannot be changed in runtime");
                }

                _serverType = value;
            }
        }
        #endregion

        #region ServerAddress
        private string _serverAddress;
        /// <summary>
        /// Get or set address of service. This is based on <see cref="ServerType"/>
        /// enumeration. It can be path to local assembly or path to remoting address/web service.
        /// </summary>
        public string ServerAddress
        {
            get
            {
                switch (ServerType)
                {
                    case ServerType.Remoting:
                        return String.Format("{0}/{1}", _serverAddress, _serverFullTypeName);

                    case ServerType.WebService:
                        return String.Empty;

                    default:
                        if (_serverAddress.StartsWith("~"))
                        {
                            _serverAddress = _serverAddress.Replace("~", "");
                            _serverAddress = String.Format
                                (
                                "{0}{1}",
                                AppDomain.CurrentDomain.BaseDirectory,
#if !MONO
                                _serverAddress.Replace('/', '\\')
#else
							 	_serverAddress
#endif
                                );
                        }

                        return _serverAddress;
                }
            }
            set
            {
                _serverAddress = value;
            }
        }
        #endregion

        #region ServerFullTypeName
        private string _serverFullTypeName;
        /// <summary>
        /// Get or set full type name of server object
        /// </summary>
        public string ServerFullName
        {
            get
            {
                return _serverFullTypeName;
            }
            set
            {
                _serverFullTypeName = value;
            }
        }
        #endregion
        
        #endregion

        #region Private

        #region Server
        private Server.IServer _serverInstance;
        private Server.IServer ServerInstance
        {
            get
            {
                if (null == _serverInstance)
                {
                    CreateServerInstance();
                }

                return _serverInstance;
            }
        }
        #endregion

        #region ObjectProxy
        /// <summary>
        /// One and only instance of proxy generator
        /// </summary>
        private static ProxyGenerator ObjectProxy
        {
            get
            {
                return ServiceFactory.ObjectProxy;
            }
        }
        #endregion

        #endregion

        #region Internal

        #region ShortImplementationType
        private string _shortImplementationType;
        internal string ShortImplementationType
        {
            get
            {
                if (null == _shortImplementationType)
                {
                    _shortImplementationType = ServerFullName.Split(',')[0];
                }

                return _shortImplementationType;
            }
        }
        #endregion

        #endregion

        #endregion

        #region Methods

        #region Public

        #region GetService
        /// <summary>
        /// Get instance of service based on implementing interface type
        /// </summary>
        /// <param name="interfaceType">Type of interface service implementes</param>
        /// <returns>Instance of service or dummy instance of implemented service</returns>
        public Server.IService GetService(Type interfaceType)
        {
            try
            {   
                IService original = ServerInstance.GetService(interfaceType);                
                return ObjectProxy.CreateInterfaceProxyWithTarget(interfaceType, original, this) as IService;
            }
            catch (Exception ex)
            {
                // TODO: Log
                ex.ToString();
            }

            return null;
        }
        /// <summary>
        /// Get instance of service based on service name
        /// </summary>
        /// <param name="serviceName">Name of service to retreive</param>
        /// <returns>Instnce of service</returns>
        public Server.IService GetService(Type interfaceType, string serviceName)
        {
            try
            {
                // TODO: Wrap with proxy class for error handling
                IService original = ServerInstance.GetService(serviceName);

                // TODO: Find only interface types and create proxy on them
                return ObjectProxy.CreateInterfaceProxyWithTarget(interfaceType, original, this) as IService;
            }
            catch (Exception ex)
            {
                // TODO: Log
                ex.ToString();
            }

            return null;
        }
        public Server.IService GetService(string name, out Type interfaceType)
        {
            interfaceType = typeof(IService);

            try
            {   
                interfaceType = GetServiceInterface(name);

                IService original = ServerInstance.GetService(interfaceType);

                return ObjectProxy.CreateInterfaceProxyWithTarget(interfaceType, original, this) as IService;
            }
            catch (Exception ex)
            {
                ex.ToString();
            }

            return null;
        }
        #endregion 

        #region FromXML
        /// <summary>
        /// Create server proxy based on configuration XML
        /// </summary>
        /// <param name="configurationXml"></param>
        /// <returns></returns>
        public static ServerInformation FromXML(string configurationXML)
        {
            try
            {
                ServerInformation server = new ServerInformation();
                XmlDocument doc = new XmlDocument();

                doc.LoadXml(configurationXML);

                XmlNode configNode = Config.GetNodeByName(doc, "server");

                if (configNode == null)
                {
                    throw new ArgumentException("Could not find 'server' node!");
                }

                server._serverAddress = Config.GetAttributeValueByName(configNode, "serverAddress");
                server._serverFullTypeName = Config.GetAttributeValueByName(configNode, "serverFullName");

                string serverType = Config.GetAttributeValueByName(configNode, "serverType");

                if (Enum.IsDefined(typeof(ServerType), serverType))
                {
                    server._serverType = (ServerType)Enum.Parse(typeof(ServerType), serverType);
                }

                return server;
            }
            catch (Exception ex)
            {
                throw new ArgumentException("Server configuration is malformed! See inner exception!", ex);
            }
        } 
        #endregion

        #region ToXML
        /// <summary>
        /// Get XML of server suitable for XML configuration
        /// </summary>
        /// <returns></returns>
        public string ToXML()
        {
            StringBuilder builder = new StringBuilder();

            builder.AppendFormat("<server serverType=\"{0}\"\n" +
                                          "\tserverAddress=\"{1}\"\n" +
                                          "\tserverFullTypeName=\"{2}\">\n",
                                          ServerType, ServerAddress, ServerFullName);
            builder.Append("</server>");

            return builder.ToString();
        }
        #endregion

        #region FromType
        /// <summary>
        /// Create server instance from type. Type must of course be loaded.
        /// Suitable for creating configuration for server
        /// </summary>
        /// <param name="serverType">Type that implements server.</param>
        /// <returns>Instance of <see cref="ServiceInformation"/></returns>
        public static ServerInformation FromType(Type serverType)
        {
            ServerInformation server = new ServerInformation();

            server.ServerFullName = serverType.AssemblyQualifiedName;
            server.ServerAddress = serverType.Assembly.Location;
            server.ServerType = File.Exists(server.ServerAddress) ? 
                ServerType.Disk : ServerType.Remoting;

            return server;
        }
        #endregion

        #region HasService
        /// <summary>
        /// Test wheater specified service type is configured at server
        /// </summary>
        /// <param name="interfaceType">Type of interface service implements</param>
        /// <returns>True if service is implemented</returns>
        public bool HasService(Type interfaceType)
        {
            try
            {
                return ServerInstance.HasService(interfaceType);
            }
            catch (Exception ex)
            {
                Logger.WriteError("Failed to test for service implementation");
                Logger.WriteException(ex);
            }

            return false;
        }
        /// <summary>
        /// Test wheater specified service type is configured at server
        /// </summary>
        /// <param name="serviceName">Name of service to test for</param>
        /// <returns>True if service is implemented</returns>
        public bool HasService(string serviceName)
        {
            try
            {
                return ServerInstance.HasService(serviceName);
            }
            catch (Exception ex)
            {
                Logger.WriteError("Failed to test for service implementation");
                Logger.WriteException(ex);
            }

            return false;
        }
        #endregion
        
        #endregion

        #region Private

        #region CreateServerInstance
        private void CreateServerInstance()
        {            
            switch (ServerType)
            {
                case ServerType.Disk:
                    System.Runtime.Remoting.ObjectHandle handle = Activator.CreateInstanceFrom(ServerAddress, ShortImplementationType);
                    _serverInstance = handle.Unwrap() as IServer;
                    break;

                case ServerType.Remoting:
                    _serverInstance = Activator.GetObject(typeof(IServer), ServerAddress) as IServer;
                    break;

                case ServerType.WebService:
                    break;
            }
        } 
        #endregion

        #region InvokeService
        private object InvokeService(IInvocation invocation, params object []args)
        {
            try
            {
                // First (default) try in case it fails instance will be recreated
                invocation.Proceed();
                return invocation.ReturnValue;
            }
            catch(Exception ex)
            {
                ex.ToString();
            }

            // Recreate server instance and try again
            // If second call fails then server is probably not available
            _serverInstance = null;
            CreateServerInstance();

            invocation.Proceed();
            return invocation.ReturnValue;
        }
        #endregion

        #region GetServiceInfo
        private Type GetServiceInterface(string name)
        {
            if (!ServiceInterfaces.ContainsKey(name))
            {
                ServiceInfo info = ServerInstance.GetServiceInfo(name);
                ServiceInterfaces.Add(name, Type.GetType(info.DefaultInterface));
            }

            return ServiceInterfaces[name];
        }
        #endregion


        #endregion

        #endregion

        #region IInterceptor Members
        /// <summary>
        /// Implemented explicitly since it's not ment for outside world to
        /// call this function
        /// </summary>
        /// <param name="invocation">Information on invocation</param>
        /// <param name="args">Arguments passed to function</param>
        /// <returns>The return value of calling function</returns>
        object Intercept(IInvocation invocation, params object[] args)
        {
            InvocationError error = null;
            object result = null;

            try
            {
                foreach (Sio.Mdm.Server.IInvokeInterceptor interceptor in ServiceFactory.Interceptors)
                {   
                    if (!interceptor.BeforeInvoke(invocation.Method, args, invocation.Proxy.ToString(), out result))
                    {
                        return result;
                    }
                }

                result = InvokeService(invocation, args);
            }
            catch (Exception ex)
            {   
                Logger.WriteError("Invocation throws exception");
                Logger.WriteException(ex);

                error = new InvocationError(ex);
            }

            foreach (Sio.Mdm.Server.IInvokeInterceptor interceptor in ServiceFactory.Interceptors)
            {
                try
                {
                    interceptor.AfterInvoke(invocation.Method, args, result, error);
                }
                catch { }
            }

            return error == null ?
                result : DummyService.GetErrorReturnValue(invocation);
        }
        #endregion

        #region IInterceptor Members

        public void Intercept(IInvocation invocation)
        {
            invocation.ReturnValue = Intercept(invocation, invocation.Arguments);
        }

        #endregion
    }

    #region ServerType
    /// <summary>
    /// Definition of possible server/client communication
    /// </summary>
    public enum ServerType
    {
        /// <summary>
        /// Server object is loaded from disk
        /// </summary>
        Disk,
        /// <summary>
        /// Server object is remoting based
        /// </summary>
        Remoting,
        /// <summary>
        /// Server object is published as web service
        /// </summary>
        WebService
    } 
    #endregion
}
